﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace DARE
{
    /// <summary>
    /// This class represents an animation. It works with sub rectangles that defines the rectangle in a sprite sheet that represents the animation step.
    /// You have to add every steps (rectangles) of an animation.
    /// </summary>
    /// <example>
    /// <code>
    ///  // creation of the CSprite
    ///  CSprite spr = new CSprite("man", m_render);
    ///  
    ///  // creation of the CAnimationState
    ///  CAnimationState animState = new CAnimationState(CResourceMgr.Load<Texture2D>("walksequence_spritesheet"), new Rectangle(0, 0, 240, 295));
    ///  
    ///  // Adding the rectangles that represent each step of the animation
    ///  animState.AddState(new Rectangle(240, 0, 240, 295));
    ///  animState.AddState(new Rectangle(480, 0, 240, 295));
    ///  animState.AddState(new Rectangle(720, 0, 240, 295));
    ///  animState.AddState(new Rectangle(960, 0, 240, 295));
    ///  animState.AddState(new Rectangle(1200, 0, 240, 295));
    ///  
    ///  // Setting of the type of animation (LOOP, PINGPONG, HANDLE)
    ///  animState.AnimationType = CAnimationState.EAnimationType.LOOP;
    ///  
    ///  // Setting of the interval between each step (in milliseconds)
    ///  animState.AnimationInterval = 20;
    ///  
    ///  // Starting the animation
    ///  animState.Start();
    ///  
    ///  // Adding the AnimationState to the sprite animations
    ///  spr.AddAnimationState("Idle", animState);
    /// </code>
    /// </example>
    public class CAnimationState
    {
        #region identifier

        public enum EAnimationType
        {
            LOOP,
            PINGPONG,
            HANDLE
        }

        public delegate void AnimationEnded(CAnimationState animationState);

        #endregion

        #region fields

        [ContentSerializer]
        private List<Rectangle> m_rectangles;
        private Texture2D m_texture;
        [ContentSerializer]
        private int m_defaultState;
        [ContentSerializer]
        private int m_currentState;
        [ContentSerializer]
        private int m_stateDirection;
        [ContentSerializer]
        private int m_defaultStateDirection;
        [ContentSerializer]
        private float m_animationInterval;
        [ContentSerializer]
        private EAnimationType m_animationType;
        [ContentSerializer]
        private bool m_isStarted;

        [ContentSerializer]
        private double m_lastGameTime = 0;
        [ContentSerializer]
        private bool m_hasEndedOnce = false;
        [ContentSerializerIgnore]
        public AnimationEnded d_animationEnded;

        #endregion

        #region properties

        public bool IsStarted { get { return m_isStarted; } }

        public float AnimationInterval { get { return m_animationInterval; } set { m_animationInterval = value; } }

        public EAnimationType AnimationType { get { return m_animationType; } set { m_animationType = value; } }

        public int DefaultState { get { return m_defaultState; } set { m_defaultState = value; } }

        public int StateDirection
        {
            get { return m_stateDirection; }
            set
            {
                if (value > 0)
                    m_defaultStateDirection = 1;
                else if (value < 0)
                    m_defaultStateDirection = -1;
                else
                    m_defaultStateDirection = 0;
                m_stateDirection = m_defaultStateDirection;
            }
        }
        [ContentSerializerIgnore]
        public Texture2D SpriteSheet
        {
            get { return m_texture; }
            set
            {
                m_texture = value;
            }
        }

        #endregion

        #region ctor

        public void ctor(Texture2D spriteSheet, Nullable<Rectangle> defaultSubRect)
        {
            m_texture = spriteSheet;
            m_isStarted = false;
            m_currentState = 0;
            m_animationInterval = 300;
            m_defaultState = 0;
            m_stateDirection = 1;
            m_defaultStateDirection = 1;
            m_rectangles = new List<Rectangle>();
            if (defaultSubRect != null)
                m_rectangles.Add(defaultSubRect.Value);
            else if (m_texture != null)
                m_rectangles.Add(new Rectangle(0, 0, m_texture.Width, m_texture.Height));
            d_animationEnded = null;
        }

        public CAnimationState()
        {
            ctor(null, null);
        }

        public CAnimationState(Texture2D spriteSheet)
        {
            ctor(spriteSheet, null);
        }

        public CAnimationState(Texture2D spriteSheet, Nullable<Rectangle> defaultSubRect)
        {
            ctor(spriteSheet, defaultSubRect);
        }

        #endregion

        #region methods

        public CAnimationState Clone()
        {
            CAnimationState anim = new CAnimationState(m_texture, (m_rectangles.Count > 0 ? new Nullable<Rectangle>(m_rectangles.First()) : null));
            anim.d_animationEnded = d_animationEnded;
            anim.m_animationInterval = m_animationInterval;
            anim.m_animationType = m_animationType;
            anim.m_currentState = m_currentState;
            anim.m_defaultState = m_defaultState;
            anim.m_defaultStateDirection = m_defaultStateDirection;
            anim.m_hasEndedOnce = m_hasEndedOnce;
            anim.m_isStarted = m_isStarted;
            anim.m_lastGameTime = m_lastGameTime;
            anim.m_rectangles = new List<Rectangle>();
            foreach (Rectangle rect in m_rectangles)
                anim.m_rectangles.Add(rect);
            anim.m_stateDirection = m_stateDirection;
            if (m_texture != null && anim.m_texture == null)
                anim.m_texture = CResourceMgr.Load<Texture2D>(CResourceMgr.GetResourceName(m_texture));
            return anim;
        }

        /// <summary>
        /// starts the animation.
        /// </summary>
        public void Start()
        {
            m_isStarted = true;
        }

        /// <summary>
        /// stops the animation
        /// </summary>
        public void Stop()
        {
            m_isStarted = false;
        }

        /// <summary>
        /// Resets the animation to the default state with the default direction.
        /// </summary>
        public void Reset()
        {
            m_currentState = m_defaultState;
            m_hasEndedOnce = false;
            m_lastGameTime = 0;
            m_stateDirection = m_defaultStateDirection;
        }

        public CAnimationState AddState(Rectangle rect)
        {
            m_rectangles.Add(rect);
            return this;
        }

        public CAnimationState RemoveState(Rectangle rect)
        {
            m_rectangles.Remove(rect);
            return this;
        }

        public CAnimationState RemoveState(int pos)
        {
            m_rectangles.RemoveAt(pos);
            return this;
        }
        
        public CAnimationState RemoveAll()
        {
            m_rectangles.Clear();
            return this;
        }

        /// <summary>
        /// sets the default state to the position given in parameters.
        /// </summary>
        /// <param name="pos">the position in the animations list.</param>
        /// <returns>this</returns>
        public CAnimationState SetDefaultState(int pos)
        {
            m_defaultState = pos;
            m_currentState = m_defaultState;
            return this;
        }

        /// <summary>
        /// sets the default state rectangle to the rectangle given in parameters
        /// </summary>
        /// <param name="rect">rectangle to be set to default state</param>
        /// <returns>this</returns>
        public CAnimationState SetDefaultState(Rectangle rect)
        {
            if (!m_rectangles.Contains(rect))
                m_rectangles.Add(rect);
            m_defaultState = m_rectangles.IndexOf(rect);
            m_currentState = m_defaultState;
            return this;
        }
        
        /// <summary>
        /// sets the current animation state (rectangle) to the rectangle given in parameters
        /// </summary>
        /// <param name="rect">rectangle to replace the current rectangle in the animations list</param>
        /// <returns>this</returns>
        public CAnimationState SetCurrentAnimationState(Rectangle rect)
        {
            if (m_currentState < m_rectangles.Count)
            {
                m_rectangles[m_currentState] = rect;
                return this;
            }
            else if (m_rectangles.Count == 0)
                m_rectangles.Add(rect);
            return this;
        }

        /// <summary>
        /// gets the current rectangle of the animation.
        /// </summary>
        /// <returns>the current rectangle</returns>
        public Rectangle GetCurrentAnimationState()
        {
            if (m_currentState < m_rectangles.Count)
                return m_rectangles[m_currentState];
            if (m_texture != null)
                return new Rectangle(0, 0, m_texture.Width, m_texture.Height);
            return Rectangle.Empty;
        }

        /// <summary>
        /// gets the current rectangle of the animation by the given gameTime (if the elapsed game time since the last state is greater than the interval between two states, it will change the current state in function of this elapsed time).
        /// </summary>
        /// <param name="gameTime">current game time</param>
        /// <returns>the current rectangle</returns>
        public Rectangle GetAnimationState(GameTime gameTime)
        {
            if (m_rectangles.Count == 0)
            {
                if (m_texture != null)
                    return new Rectangle(0, 0, m_texture.Width, m_texture.Height);
                else
                    return Rectangle.Empty;
            }
            if (!m_isStarted)
                return m_rectangles[m_currentState];
            int nbAnims = (int)(m_lastGameTime / m_animationInterval);
            if (nbAnims == 0)
            {
                m_lastGameTime += gameTime.ElapsedGameTime.TotalMilliseconds;
                return m_rectangles[m_currentState];
            }

            m_lastGameTime = 0;
            switch (m_animationType)
            {
                case EAnimationType.LOOP:
                    for (int i = 0; i < nbAnims; ++i)
                    {
                        if (m_currentState + m_stateDirection >= m_rectangles.Count)
                        {
                            m_currentState = m_defaultState;
                            if (d_animationEnded != null)
                                d_animationEnded(this);
                            m_hasEndedOnce = true;
                        }
                        else if (m_currentState + m_stateDirection <= 0)
                            m_currentState = m_rectangles.Count - 1;
                        else
                            m_currentState += m_stateDirection;
                    }
                    break;
                case EAnimationType.PINGPONG:
                    for (int i = 0; i < nbAnims; ++i)
                    {
                        if (m_currentState + m_stateDirection >= m_rectangles.Count || m_currentState + m_stateDirection < 0)
                        {
                            m_stateDirection *= -1;
                            if (d_animationEnded != null)
                                d_animationEnded(this);
                            m_hasEndedOnce = true;
                        }
                        m_currentState += m_stateDirection;
                    }
                    break;
                case EAnimationType.HANDLE:
                    for (int i = 0; i < nbAnims; ++i)
            	    {
                        if (m_currentState + m_stateDirection >= m_rectangles.Count || (m_currentState == 0 && m_stateDirection < 0))
                        {
                            m_stateDirection = 0;
                            if (d_animationEnded != null)
                                d_animationEnded(this);
                            m_hasEndedOnce = true;
                        }
                        m_currentState += m_stateDirection;
            	    }
                    break;
                default:
                    return Rectangle.Empty;
            }
            return m_rectangles[m_currentState];
        }

        #endregion
    }
}
